Defining Generic Methods in Java
Course: Core Java Training
Topic: Defining Generic Methods in Java
Course Link: Core Java Training Course
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to Generic Methods
Generic methods in Java are methods that declare their own type parameters. They allow a single method to work with different data types while maintaining compile-time type safety. Instead of writing separate methods for String, Integer, Double, or other reference types, a generic method can handle multiple types through a type parameter.
Generic methods are useful when the same operation must be performed on different kinds of objects. Common examples include displaying values, comparing objects, searching arrays, swapping elements, and processing collections.
Generic methods can be declared inside generic or non-generic classes. Their type parameters belong to the method itself and have a scope limited to that method's declaration.
Learning Objectives
- Understand the meaning and benefits of generic methods.
- Learn the syntax for defining generic methods.
- Use generic methods with different data types.
- Understand type inference and explicit type arguments.
- Define generic methods with multiple type parameters.
- Apply bounded type parameters and wildcards.
- Understand generic methods in static and non-static contexts.
- Identify common errors and best practices.
2. Why Do We Need Generic Methods?
Consider a program that displays values of different types. Without generics, a developer might write separate methods for String, Integer, and Double. This approach duplicates logic and becomes difficult to maintain as more types are introduced.
Example Without Generic Methods
public class WithoutGenericMethod {
public static void displayString(String value) {
System.out.println(value);
}
public static void displayInteger(Integer value) {
System.out.println(value);
}
public static void displayDouble(Double value) {
System.out.println(value);
}
public static void main(String[] args) {
displayString("Java");
displayInteger(100);
displayDouble(25.5);
}
}
Output:
Java
100
25.5
This program works, but it repeats the same printing logic in multiple methods. A generic method can perform the same task using one reusable implementation.
Example Using a Generic Method
public class WithGenericMethod {
public static <T> void display(T value) {
System.out.println(value);
}
public static void main(String[] args) {
display("Java");
display(100);
display(25.5);
display(true);
}
}
Output:
Java
100
25.5
true
The method works with different reference types, including wrapper classes used when primitive values are passed through Java's autoboxing feature.
3. Syntax of a Generic Method
The type parameter declaration appears before the method's return type. It is enclosed in angle brackets.
General Syntax
accessModifier static <T> returnType methodName(T parameter) {
// Method body
}
For example:
public static <T> void printValue(T value) {
System.out.println(value);
}
Parts of a Generic Method
| Part |
Meaning |
public |
Access modifier that makes the method available wherever permitted by the class's visibility. |
static |
Allows the method to be called through the class without creating an instance. |
<T> |
Declares a type parameter named T. |
void |
Indicates that the method does not return a value. |
printValue |
The method name. |
T value |
A parameter whose type is represented by T. |
Important: The type parameter list must appear before the return type. Writing public static void <T> printValue(T value) is invalid Java syntax.
4. Creating a Simple Generic Method
Let's create a generic method that displays a value. The compiler determines the appropriate type argument from the value supplied in the method call.
public class GenericMethodExample {
public static <T> void display(T value) {
System.out.println("Value: " + value);
}
public static void main(String[] args) {
display("Hello Java");
display(250);
display(19.75);
display('A');
}
}
Output:
Value: Hello Java
Value: 250
Value: 19.75
Value: A
Explanation
- The method declares its own type parameter using
<T>.
- The parameter
value uses type T.
- The method can be called with values of different compatible types.
- The compiler infers the type argument at each invocation.
- The method implementation is written once and reused.
5. Generic Methods and Type Parameters
A type parameter is a placeholder for a type. When a generic method is called, the compiler determines or infers the type argument that fits the invocation.
For example, in the following method:
public static <T> T getValue(T value) {
return value;
}
T is used both as the input parameter type and as the return type. This expresses a relationship: the method returns a value of the same type as its argument.
String name = getValue("Rahul");
Integer age = getValue(25);
System.out.println(name);
System.out.println(age);
Output:
Rahul
25
This relationship is an important advantage of generic methods. The return type can be determined from the input type without casting.
6. Generic Methods with Return Values
A generic method can return a value of its type parameter. This is useful when a method selects, transforms, or returns an object while preserving its type.
Example: Returning a Value
public class GenericReturnExample {
public static <T> T returnValue(T value) {
return value;
}
public static void main(String[] args) {
String language = returnValue("Java");
Integer number = returnValue(500);
Double price = returnValue(99.99);
System.out.println(language);
System.out.println(number);
System.out.println(price);
}
}
Output:
Java
500
99.99
Because the method returns T, the compiler can assign the result to the appropriate type without an explicit cast.
7. Generic Methods with Multiple Parameters
A generic method can accept multiple parameters that use the same type parameter. This is useful when the method needs to compare or process values that should be of a compatible type.
Example: Displaying Two Values of the Same Type
public class SameTypeParameters {
public static <T> void displayPair(T first, T second) {
System.out.println("First: " + first);
System.out.println("Second: " + second);
}
public static void main(String[] args) {
displayPair("Java", "Generics");
displayPair(10, 20);
}
}
Output:
First: Java
Second: Generics
First: 10
Second: 20
The same type parameter is used for both arguments. The compiler attempts to infer a type that makes the complete method invocation valid. Depending on the types and context, it may infer a common compatible type rather than requiring the two expressions to have identical declared types.
8. Generic Methods with Multiple Type Parameters
Sometimes method parameters represent different kinds of data. In that case, a method can declare more than one type parameter.
Syntax
public static <T, U> void methodName(T first, U second) {
// Method body
}
Example: Displaying Different Types
public class MultipleTypeParameters {
public static <T, U> void displayPair(T first, U second) {
System.out.println("First value: " + first);
System.out.println("Second value: " + second);
}
public static void main(String[] args) {
displayPair("Age", 25);
displayPair("Price", 149.99);
displayPair(100, true);
}
}
Output:
First value: Age
Second value: 25
First value: Price
Second value: 149.99
First value: 100
Second value: true
Here, T and U are independent type parameters. The method can accept two values of different types without converting them to a shared declared parameter type such as Object.
9. Generic Methods in Non-Generic Classes
A generic method does not require its enclosing class to be generic. A regular class can contain a method that declares its own type parameter.
public class Utility {
public <T> void print(T value) {
System.out.println(value);
}
}
Usage:
public class UtilityDemo {
public static void main(String[] args) {
Utility utility = new Utility();
utility.print("Hello");
utility.print(100);
utility.print(3.14);
}
}
Output:
Hello
100
3.14
The Utility class is not generic, but its print method is generic because it declares <T> in its own method signature.
10. Generic Methods Inside Generic Classes
A generic class can contain ordinary methods that use the class's type parameter, as well as generic methods that declare their own type parameters.
class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public <U> void displayAdditionalValue(U additionalValue) {
System.out.println("Box value: " + value);
System.out.println("Additional value: " + additionalValue);
}
}
public class GenericClassMethodDemo {
public static void main(String[] args) {
Box<String> box = new Box<>("Java");
box.displayAdditionalValue(100);
}
}
Output:
Box value: Java
Additional value: 100
In this example, T belongs to the Box class, while U belongs only to the generic method. The method can accept an additional value of a type different from the type stored in the box.
11. Static Generic Methods
A static generic method declares its own type parameter. Since static methods do not belong to a particular object instance, they cannot directly use an instance-specific class type parameter unless that type is declared independently by the method.
Example
public class StaticGenericExample {
public static <T> void show(T value) {
System.out.println(value);
}
public static void main(String[] args) {
StaticGenericExample.show("Java");
StaticGenericExample.show(200);
}
}
Output:
Java
200
Static generic methods are commonly used in utility classes, conversion helpers, validation methods, and reusable algorithms.
12. Non-Static Generic Methods
A non-static generic method belongs to an object instance. It can use instance fields and methods while also declaring its own type parameter.
public class MessageService {
private String prefix = "Message: ";
public <T> void printMessage(T value) {
System.out.println(prefix + value);
}
public static void main(String[] args) {
MessageService service = new MessageService();
service.printMessage("Welcome");
service.printMessage(2026);
}
}
Output:
Message: Welcome
Message: 2026
The method uses the instance field prefix and accepts different types through its own type parameter.
13. Type Inference in Generic Methods
Type inference allows the Java compiler to determine a generic method's type arguments from the method invocation and its context. This means callers usually do not need to specify type arguments explicitly.
Example: Inferred Type Arguments
public class TypeInferenceExample {
public static <T> T identity(T value) {
return value;
}
public static void main(String[] args) {
String text = identity("Java");
Integer number = identity(100);
System.out.println(text);
System.out.println(number);
}
}
Output:
Java
100
The compiler infers String for the first invocation and Integer for the second.
Explicit Type Arguments
In some cases, you can provide the type argument explicitly:
String text = TypeInferenceExample.<String>identity("Hello");
Explicit type arguments can be useful when inference is unclear or when you want to make the intended type obvious. In ordinary cases, inference keeps code shorter and easier to read.
14. Bounded Generic Methods
A bounded generic method restricts the types that can be used for its type parameter. The extends keyword introduces an upper bound, which may be a class or an interface.
Syntax
public static <T extends Number> void methodName(T value) {
// Method body
}
This declaration accepts Number and its subclasses, including Integer, Double, and Float.
Example: Working with Numeric Values
public class BoundedMethodExample {
public static <T extends Number> double square(T value) {
double number = value.doubleValue();
return number * number;
}
public static void main(String[] args) {
System.out.println(square(5));
System.out.println(square(2.5));
// Compile-time error:
// square("Java");
}
}
Output:
25.0
6.25
The bound permits the method to call doubleValue(), which is defined by Number. A String cannot be passed because it does not extend Number.
15. Bounded Methods Using Comparable
The Comparable<T> interface defines a compareTo() method. A generic method can use this interface as a bound when it needs to compare values according to their natural ordering.
Example: Finding the Greater Value
public class ComparableMethodExample {
public static <T extends Comparable<T>> T greater(
T first, T second) {
if (first.compareTo(second) >= 0) {
return first;
}
return second;
}
public static void main(String[] args) {
System.out.println(greater(10, 20));
System.out.println(greater("Apple", "Banana"));
}
}
Output:
20
Banana
The method can compare values whose types implement the required Comparable relationship. The meaning of greater depends on the type's natural ordering.
16. Generic Methods with Wildcards
Wildcards allow methods to accept generic collections whose exact type argument is unknown. A wildcard is represented by the question mark (?).
Unbounded Wildcard Example
import java.util.List;
public class WildcardMethodExample {
public static void printList(List<?> values) {
for (Object value : values) {
System.out.println(value);
}
}
public static void main(String[] args) {
printList(List.of("Java", "Python"));
printList(List.of(10, 20, 30));
}
}
Output:
Java
Python
10
20
30
The method can accept lists of different element types. Because the exact element type is unknown, elements are read as Object and arbitrary non-null values cannot be added safely.
17. Generic Methods with Upper Bounded Wildcards
An upper bounded wildcard uses ? extends Type. It is helpful when a method needs to read values from collections containing a particular type or its subtypes.
import java.util.List;
public class UpperBoundMethodExample {
public static double sum(List<? extends Number> values) {
double total = 0;
for (Number value : values) {
total += value.doubleValue();
}
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(10, 20, 30)));
System.out.println(sum(List.of(1.5, 2.5)));
}
}
Output:
60.0
4.0
The method accepts lists of Integer, Double, or other Number subclasses. It can read the values as Number, but it cannot safely add an arbitrary Number to a list whose exact element type is unknown.
18. Generic Methods with Lower Bounded Wildcards
A lower bounded wildcard uses ? super Type. It allows a method to accept a collection whose element type is the specified type or one of its supertypes. This is useful when adding values to a collection.
import java.util.List;
public class LowerBoundMethodExample {
public static void addValues(List<? super Integer> values) {
values.add(10);
values.add(20);
values.add(30);
}
public static void main(String[] args) {
List<Number> numbers = new java.util.ArrayList<>();
addValues(numbers);
System.out.println(numbers);
}
}
Output:
[10, 20, 30]
Using ? super Integer allows Integer values to be added to lists of Integer, Number, or Object. Values read from the list are guaranteed only to be Objects without additional type information.
19. Generic Method vs. Generic Class
Generic classes and generic methods both use type parameters, but their scopes and purposes differ.
| Feature |
Generic Class |
Generic Method |
| Declaration |
Type parameter follows the class name. |
Type parameter appears before the method's return type. |
| Scope |
Available throughout the relevant class declaration. |
Available within the method declaration and its applicable scope. |
| Example |
class Box<T> |
<T> T identity(T value) |
| Type selection |
Chosen when the parameterized class type is used. |
Often inferred separately for each method invocation. |
| Typical use |
Representing a reusable data structure. |
Performing a reusable operation on different types. |
20. Generic Methods vs. Method Overloading
Method overloading means declaring multiple methods with the same name but different parameter lists. Generic methods offer another way to reuse an operation across different types.
// Overloaded methods
void display(String value) {
System.out.println(value);
}
void display(Integer value) {
System.out.println(value);
}
// Generic method
<T> void show(T value) {
System.out.println(value);
}
Overloading is useful when different types require different implementations. A generic method is useful when the same logic can safely work with many types.
21. Common Errors When Defining Generic Methods
| Error |
Reason |
Solution |
Placing <T> after the return type |
The type parameter list is in the wrong position. |
Place the type parameter declaration before the return type. |
| Passing an incompatible type |
The argument does not satisfy the inferred type or declared bounds. |
Pass a compatible value or adjust the type declaration appropriately. |
Using > to compare generic objects |
Relational operators do not compare arbitrary reference objects. |
Use a suitable bound such as T extends Comparable<T> and call compareTo(). |
| Calling a bounded method with String |
String does not satisfy a Number bound. |
Pass a Number subtype or choose a different bound that matches the requirement. |
Attempting to create new T() |
The type parameter is not available for direct object construction. |
Use a factory, constructor reference, or explicit creation strategy. |
| Using raw types |
Raw types weaken compile-time checking. |
Prefer parameterized types and avoid unchecked operations. |
22. Best Practices for Defining Generic Methods
- Use generic methods when the same operation can safely work with multiple types.
- Declare type parameters before the return type.
- Use conventional names such as T, E, K, and V.
- Use multiple type parameters only when they represent meaningful type relationships.
- Use bounds when the method needs to call methods provided by a superclass or interface.
- Use wildcards when they provide flexibility without requiring an additional named type parameter.
- Prefer type inference when the intended type is clear from the method call.
- Return T when the method needs to preserve the relationship between an input type and its result.
- Avoid unnecessary casts and raw types.
- Keep generic method signatures readable and document complex bounds when needed.
23. Practical Example: Generic Array Search
This example defines a generic method that searches an array for a target value using equals(). It works with reference types that implement suitable equality behavior.
public class GenericArraySearch {
public static <T> int findIndex(T[] values, T target) {
for (int i = 0; i < values.length; i++) {
if (java.util.Objects.equals(values[i], target)) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
String[] names = {"Amit", "Neha", "Rahul"};
Integer[] numbers = {10, 20, 30, 40};
System.out.println(findIndex(names, "Rahul"));
System.out.println(findIndex(numbers, 30));
System.out.println(findIndex(names, "Priya"));
}
}
Output:
2
2
-1
Explanation
- The method declares T as its type parameter.
- The array and target parameter both use T.
- The method checks each element against the target.
Objects.equals() safely compares values, including null values.
- The method returns the matching index or -1 when no match is found.
The search uses a linear scan, so its time complexity is O(n), where n is the number of elements in the array.
24. Practical Example: Generic Swap Method
A generic swap method exchanges two elements in an array. The method can work with arrays of different reference types.
public class GenericSwapExample {
public static <T> void swap(T[] values, int first, int second) {
T temp = values[first];
values[first] = values[second];
values[second] = temp;
}
public static void main(String[] args) {
String[] names = {"Amit", "Neha", "Rahul"};
Integer[] numbers = {10, 20, 30};
swap(names, 0, 2);
swap(numbers, 0, 1);
System.out.println(java.util.Arrays.toString(names));
System.out.println(java.util.Arrays.toString(numbers));
}
}
Output:
[Rahul, Neha, Amit]
[20, 10, 30]
In production code, validate the indexes before accessing the array. Invalid indexes cause ArrayIndexOutOfBoundsException.
25. Practice Exercises
- Create a generic method that prints any supplied value.
- Write a generic method that returns its argument unchanged.
- Create a generic method that accepts two values of the same type and displays them.
- Create a generic method with two type parameters to display a name and an age.
- Write a generic method that searches an array and returns the matching index.
- Create a bounded generic method that calculates the square of a Number value.
- Write a generic method using
Comparable to return the greater of two values.
- Create a method that prints the elements of any list using
List<?>.
- Write a method that accepts
List<? extends Number> and calculates the sum.
- Write a method that accepts
List<? super Integer> and adds integer values.
- Compare a generic method with two overloaded methods that perform the same operation.
26. Mini Project: Generic Utility Library
Project Objective: Create a small Java utility library that demonstrates reusable generic methods for common programming operations.
Requirements
- Create a generic
display() method that prints a value.
- Create an
identity() method that returns the supplied value.
- Create a generic array search method that returns an index or -1.
- Create a generic swap method for exchanging two array elements.
- Create a bounded method that finds the greater of two comparable values.
- Create a method that calculates the sum of a list of Number values.
- Test the methods with String, Integer, Double, and custom objects where appropriate.
- Document the purpose and type restrictions of each method.
Expected Learning Outcome
After completing the project, learners should be able to define generic methods, apply type inference, use bounds, and design reusable utility methods with clear type relationships.
27. Interview Questions and Answers
Q1. What is a generic method in Java?
Answer: A generic method declares its own type parameter and can operate on different types while preserving compile-time type safety.
Q2. What is the syntax of a generic method?
Answer: A generic method places its type parameter list before the return type, such as public static <T> T identity(T value).
Q3. Can a non-generic class contain a generic method?
Answer: Yes. A regular class can declare a method with its own type parameter.
Q4. Can generic methods be static?
Answer: Yes. A static generic method can declare its own type parameter and can be called without creating an instance.
Q5. What is type inference?
Answer: Type inference is the compiler's ability to determine suitable type arguments from a generic method invocation and its context.
Q6. Can a generic method have multiple type parameters?
Answer: Yes. For example, <T, U> allows the method to represent two potentially different types.
Q7. What is a bounded generic method?
Answer: It is a generic method whose type parameter is restricted by a bound, such as <T extends Number>.
Q8. How can a generic method compare two objects?
Answer: It can use a bound such as T extends Comparable<T> and compare the values with compareTo(), or accept a suitable Comparator.
Q9. What is the difference between a generic method and a generic class?
Answer: A generic class declares type parameters at the class level, while a generic method declares its own type parameters at the method level.
Q10. Can a generic method return a type parameter?
Answer: Yes. For example, public static <T> T identity(T value) returns a value of type T.
Q11. Why can't we use the greater-than operator with generic objects?
Answer: The > operator is not defined for arbitrary reference types. Use a suitable comparison contract such as Comparable or a Comparator.
Q12. When should wildcards be used instead of a named type parameter?
Answer: Use wildcards when the method only needs to accept a range of generic argument types and does not need to express a relationship between that type and another parameter or the return type.
28. Quick Revision Checklist
- A generic method declares its own type parameter.
- The type parameter list appears before the return type.
<T> represents one type parameter.
<T, U> declares two type parameters.
- Generic methods can be static or non-static.
- Type inference usually determines the method's type arguments automatically.
- Bounded methods restrict types using declarations such as
<T extends Number>.
<T extends Comparable<T>> supports natural comparisons through compareTo().
- Wildcards help methods accept flexible generic collections.
- Generic methods reduce duplicate code and improve type safety.
29. Conclusion
Generic methods are a powerful feature of Core Java that allows developers to write reusable operations for different data types. By declaring type parameters, using return types that preserve type relationships, and applying bounds or wildcards when necessary, developers can build flexible utility methods without sacrificing compile-time checking.
Understanding generic methods is valuable when working with collections, arrays, reusable libraries, and Java APIs. Practice the examples and exercises to strengthen your ability to design clear and type-safe generic methods.
Learn More with JustAcademy
Explore the Core Java Training Course to learn more about Java fundamentals, object-oriented programming, collections, generics, exception handling, and other important Java concepts.
Interested in a course demonstration? Register for a Course Demo.
Visit the JustAcademy official website to explore available training programs.